from .records import *
from .geometry import calculate_geometry
from .context import Context
from .util import IndexedStruct
from .error import InvalidSuperblock
import collections
import itertools
import logging
import os
import pdb
import struct

logger = logging.getLogger('fs')


class SingleOp:

    def __init__(self, fs):
        self._fs = fs

    def __getattr__(self, attr):
        def wrapper(*args, **kwds):
            with self._fs as ctx:
                return getattr(ctx, attr)(*args, **kwds)
        return wrapper


class Filesystem:
    __slots__ = ('__dev', '_geo', '_blocks', '_indirect_struct', '_context', '_inorefs', '_handles', '_paths', '_alloc_ptr', 'single_op')

    def __init__(self, device_path):
        self.__dev = open(device_path, "r+b")
        data = self.__dev.read(BootRecord.size)
        bootrec = BootRecord.unpack(data)
        if bootrec['fsident'].split(b'\0', 1)[0] != b'cpfs1':
            raise InvalidSuperblock(
                'Invalid filesystem identifier: {!r}'.format(bootrec['fsident']))
        self.__dev.seek(0, os.SEEK_END)
        self._geo = calculate_geometry(
            self.__dev.tell() // bootrec['blksize'],
            bootrec['blksize'])
        logger.debug('%r', self._geo)
        self._blocks = {}
        self._indirect_struct = IndexedStruct(
            Blkno,
            self._geo.block_size // struct.calcsize(Blkno))
        self._context = None
        self._inorefs = {}
        self._handles = {}
        self._paths = {}
        self._alloc_ptr = 0
        self.single_op = SingleOp(self)

    def _dev_read(self, offset, count):
        self.__dev.seek(offset)
        buffer = self.__dev.read(count)
        assert len(buffer) == count, (len(buffer), count)
        return buffer

    def _dev_write(self, buffer, offset):
        self.__dev.seek(offset)
        self.__dev.write(buffer)
        logger.debug('Wrote %i bytes to device at offset %i', len(buffer), offset)
        #self._dev.seek(-1)

    def _block_put(self, blkno, block):
        if blkno in self._blocks:
            logger.log(9, 'Comparing blocks, %r, %r', block, self._blocks[blkno])
            if block == self._blocks[blkno]:
                return
        self._dev_write(block, blkno * self._geo.block_size)
        self._blocks[blkno] = bytes(block)
        logger.debug('Wrote block %i to device', blkno)

    def _block_get(self, blkno):
        if blkno in self._blocks:
            return self._blocks[blkno]
        self._blocks[blkno] = self._dev_read(
            blkno * self._geo.block_size, self._geo.block_size)
        return self._block_get(blkno)

    def __enter__(self):
        assert self._context is None, self._context
        self._context = Context(self)
        logger.debug('Attached filesystem context')
        return self._context

    def __exit__(self, type, val, tb):
        try:
            if not any([type, val, tb]):
                self._context.commit()
        finally:
            self._context = None
            logger.debug('Detached filesystem context')

    def close(self):
        handles = self._handles.keys()
        with self as context:
            for fh in handles:
                context.close(fh)
        assert not self._handles
        assert not self._inorefs
        self.__dev.close()
